-
Notifications
You must be signed in to change notification settings - Fork 0
/
DiscoveryConfig.cs
166 lines (153 loc) · 7.93 KB
/
DiscoveryConfig.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
using Microsoft.Extensions.Configuration;
using System.Collections;
using System.Collections.Frozen;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Mail;
using System.Text.Json.Serialization;
using wan24.Core;
using wan24.ObjectValidation;
namespace wan24.AutoDiscover.Models
{
/// <summary>
/// Discovery configuration
/// </summary>
public record class DiscoveryConfig : ValidatableRecordBase
{
/// <summary>
/// Discovery configuration type
/// </summary>
protected Type? _DiscoveryType = null;
/// <summary>
/// Constructor
/// </summary>
public DiscoveryConfig() : base() { }
/// <summary>
/// Current configuration
/// </summary>
public static DiscoveryConfig Current { get; set; } = null!;
/// <summary>
/// Logfile path
/// </summary>
[StringLength(short.MaxValue, MinimumLength = 1)]
public string? LogFile { get; init; }
/// <summary>
/// Number of POX XML responses to pre-fork
/// </summary>
[Range(1, int.MaxValue)]
public int PreForkResponses { get; init; } = 10;
/// <summary>
/// Dicovery configuration type name
/// </summary>
[StringLength(byte.MaxValue, MinimumLength = 1)]
public string? DiscoveryTypeName { get; init; }
/// <summary>
/// Discovery configuration type
/// </summary>
[JsonIgnore]
public virtual Type DiscoveryType => _DiscoveryType ??= string.IsNullOrWhiteSpace(DiscoveryTypeName)
? typeof(Dictionary<string, DomainConfig>)
: TypeHelper.Instance.GetType(DiscoveryTypeName)
?? throw new InvalidDataException($"Discovery type {DiscoveryTypeName.ToQuotedLiteral()} not found");
/// <summary>
/// Known http proxies
/// </summary>
public IReadOnlySet<IPAddress> KnownProxies { get; init; } = new HashSet<IPAddress>();
/// <summary>
/// JSON file path which contains the email mappings list
/// </summary>
[StringLength(short.MaxValue, MinimumLength = 1)]
public string? EmailMappings { get; init; }
/// <summary>
/// Watch email mappings list file changes for reloading the configuration?
/// </summary>
public bool WatchEmailMappings { get; init; } = true;
/// <summary>
/// Additional file paths to watch for an automatic configuration reload
/// </summary>
[CountLimit(1, byte.MaxValue), ItemStringLength(short.MaxValue)]
public string[]? WatchFiles { get; init; }
/// <summary>
/// Command to execute (and optional arguments) before reloading the configuration
/// </summary>
[CountLimit(1, byte.MaxValue), ItemStringLength(short.MaxValue)]
public string[]? PreReloadCommand { get; init; }
/// <summary>
/// Get the discovery configuration
/// </summary>
/// <param name="config">Configuration</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Discovery configuration</returns>
public virtual async Task<IReadOnlyDictionary<string, DomainConfig>> GetDiscoveryConfigAsync(IConfigurationRoot config, CancellationToken cancellationToken = default)
{
Type discoveryType = DiscoveryType;
if (!typeof(IDictionary).IsAssignableFrom(discoveryType))
throw new InvalidDataException($"Discovery type must be an {typeof(IDictionary)}");
if (!discoveryType.IsGenericType)
throw new InvalidDataException($"Discovery type must be a generic type");
// Validate discovery configuration type generic type arguments
Type[] gt = discoveryType.GetGenericArguments();
if (gt.Length != 2)
throw new InvalidDataException($"Discovery type must be a generic type with two type arguments");
if (gt[0] != typeof(string))
throw new InvalidDataException($"Discovery types first generic type argument must be {typeof(string)}");
if (!typeof(DomainConfig).IsAssignableFrom(gt[1]))
throw new InvalidDataException($"Discovery types second generic type argument must be a {typeof(DomainConfig)}");
// Parse the discovery configuration
IDictionary discovery = config.GetRequiredSection("DiscoveryConfig:Discovery").Get(discoveryType) as IDictionary
?? throw new InvalidDataException("Failed to get discovery configuration from the \"DiscoveryConfig:Discovery section\"");
object[] keys = new object[discovery.Count],
values = new object[discovery.Count];
discovery.Keys.CopyTo(keys, index: 0);
discovery.Values.CopyTo(values, index: 0);
Dictionary<string, DomainConfig> discoveryDomains = new(
Enumerable.Range(0, discovery.Count).Select(i => new KeyValuePair<string, DomainConfig>((string)keys[i], (DomainConfig)values[i]))
);
// Apply email mappings
if (!string.IsNullOrWhiteSpace(EmailMappings))
if (File.Exists(EmailMappings))
{
Logging.WriteInfo($"Loading email mappings from {EmailMappings.ToQuotedLiteral()}");
FileStream fs = FsHelper.CreateFileStream(EmailMappings, FileMode.Open, FileAccess.Read, FileShare.Read);
EmailMapping[] mappings;
await using (fs.DynamicContext())
mappings = await JsonHelper.DecodeAsync<EmailMapping[]>(fs, cancellationToken).DynamicContext()
?? throw new InvalidDataException("Invalid email mappings");
foreach(EmailMapping mapping in mappings)
{
if (!mapping.Email.Contains('@'))
{
if (Logging.Debug)
Logging.WriteDebug($"Skipping invalid email address {mapping.Email.ToQuotedLiteral()}");
continue;
}
string email = mapping.Email.ToLower();
string[] emailParts = email.Split('@', 2);
if (
emailParts.Length != 2 ||
!MailAddress.TryCreate(email, out MailAddress? emailAddress) ||
(emailAddress.User.Length == 1 && (emailAddress.User[0] == '*' || emailAddress.User[0] == '@')) ||
EmailMapping.GetLoginUser(mappings, email) is not string loginUser ||
DomainConfig.GetConfig(string.Empty, emailParts) is not DomainConfig domain
)
{
if (Logging.Debug)
Logging.WriteDebug($"Mapping email address {email.ToQuotedLiteral()} to login user failed, because it seems to be a redirection to an external target, or no matching domain configuration was found");
continue;
}
if (Logging.Debug)
Logging.WriteDebug($"Mapping email address {email.ToQuotedLiteral()} to login user {loginUser.ToQuotedLiteral()}");
domain.LoginNameMapping ??= [];
if (Logging.Debug && domain.LoginNameMapping.ContainsKey(email))
Logging.WriteDebug($"Overwriting existing email address {email.ToQuotedLiteral()} mapping");
domain.LoginNameMapping[email] = loginUser;
}
}
else
{
Logging.WriteWarning($"Email mappings file {EmailMappings.ToQuotedLiteral()} not found");
}
return discoveryDomains.ToFrozenDictionary();
}
}
}